/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

// 包含所有FA模型的测试用例。
// 单框架hos sdk不包含api8，相应用例可能无法通过，是正常现象。

import { describe, expect, test } from '@jest/globals';
import { Json5Reader, parseJsonText } from '@ohos/hvigor';
import fs from 'fs';
import * as fse from 'fs-extra';
import { copy, readdirSync } from 'fs-extra';
import path, { basename, resolve } from 'path';

import { BuildOpt } from '../../../src/options/build/build-opt';
import { ModuleBuildProfile } from '../../../src/options/build/module-build-profile';
import { ProjectBuildProfile } from '../../../src/options/build/project-build-profile';
import { ConfigJson } from '../../../src/options/configure/config-json-options';
import { OhosLogger } from '../../../src/utils/log/ohos-logger';
import { testSignFalse, testSignTrue } from '../build-app-sign.longtest';
import { testHapSignFalse, testHapSignTrue } from '../build-hap-sign.longtest';
import { buildModeInitial } from '../build-mode.test';
import {
  addOhosTestDependency,
  appRoot,
  initByFAEmptyApp,
  initByFAEmptyService,
  initByStageEmptyApp,
  initExecuteCommand,
  runHvigor,
  runShouldFail
} from '../common/common';
import { ConfigJsonModifier } from '../common/modifier/config-json-modifier';
import { ModuleBuildProfileModifier } from '../common/modifier/module-build-profile-modifier';
import { ModuleJsonModifier } from '../common/modifier/module-json-modifier';
import { PackageJsonModifier } from '../common/modifier/package-json-modifier';
import { ProjectBuildProfileModifier } from '../common/modifier/project-build-profile-modifier';
import { Probes } from '../common/probes';
import { generateDefaultFABuildConfig, testPreview, testPreviewResource } from '../common/task/common-preview';
import { prepareProcessLibs, testProcessLibs } from '../process-libs.longtest';
import { readSyncOutput, testSync } from '../sync.test';
import { traceTest } from '../trace.test';
import ConfigOptObj = ConfigJson.ConfigOptObj;
import ModuleBuildOpt = ModuleBuildProfile.ModuleBuildOpt;
import ModuleTargetBuildOpt = ModuleBuildProfile.ModuleTargetBuildOpt;
import ProjectProfileOpt = ProjectBuildProfile.ProjectProfileOpt;
import { getJson5Obj } from '../../../src/utils/json-util';

describe('business.validate.test', () => {
  test.concurrent('Empty entryModules field - FA', async () => {
    await initByFAEmptyApp({
      cb: async (dir) => {
        const moduleBuildProfileModifier = new ModuleBuildProfileModifier(dir, 'feature');
        moduleBuildProfileModifier.setEntryModules([]).outputSync();
        const commands = initExecuteCommand({
          mode: 'module',
          tasks: 'PreBuild',
        });
        runShouldFail(dir, commands, 'The entryModule field cannot be empty, because a feature module must ' +
          'have a corresponding entry module. This might happen when an entry module was removed.');
      }
    });
  });

  test.concurrent('device type phone - stage', async () => {
    await initByStageEmptyApp({
      cb: async (dir) => {
        const moduleJsonModifier = new ModuleJsonModifier(dir, 'entry');
        moduleJsonModifier.setDeviceType(['phone']).outputSync();
        const commands = initExecuteCommand({
          mode: 'module',
          tasks: 'default@PreBuild',
        });
        const result = runHvigor(dir, commands);
        expect(result.stderr).toContain('Currently, the device type \'phone\' is not supported in module.json5 ' +
          'or config.json. You are advised to change the device type to \'default\'.');
      }
    });
  });
});

describe('hos-api9-stage-test', () => {
  test.concurrent('FA service with API 9', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'default@LegacyPreBuild'
      });
      new ModuleBuildProfileModifier(projectRoot, 'entry').updateTarget({
        name: 'default',
        runtimeOS: 'HarmonyOS'
      }).outputSync();
      new ProjectBuildProfileModifier(projectRoot).setSdkVersion(9).setCompatibleSdkVersion(9).outputSync();
      const result = runHvigor(projectRoot, commands);
      expect(result.stdout.includes('BUILD SUCCESS')).toBeTruthy();
    });
  });

  test.concurrent('FA App with API 9 & service widget', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'default@LegacyPreBuild'
      });
      new ModuleBuildProfileModifier(projectRoot, 'entry').updateTarget({
        name: 'default',
        runtimeOS: 'HarmonyOS'
      }).outputSync();

      // 本身已经有卡片，只需要改api版本
      new ProjectBuildProfileModifier(projectRoot).setSdkVersion(9).setCompatibleSdkVersion(9).outputSync();
      const result = runHvigor(projectRoot, commands);
      expect(result.stdout.includes('BUILD SUCCESS')).toBeTruthy();
    });
  });
});

describe('hos-library.test', () => {
  test.concurrent('FA with runtimeOS HarmonyOs library', async () => {
    await initByFAEmptyApp({
      templateVersion: '20220530',
      prepare: async (dir) => {
        // copy extra resources
        await copy(resolve(appRoot(), 'test/resources/extra/har'), dir);
        const entryPackageJson = new PackageJsonModifier(dir, 'entry');
        entryPackageJson.updateDependency('library', 'file:../library');
        entryPackageJson.outputSync();
      },
      cb: async (dir) => {
        const moduleBuildProfileModifier = new ModuleBuildProfileModifier(dir, 'entry');
        moduleBuildProfileModifier.updateTarget({
          name: 'default',
          runtimeOS: 'HarmonyOS'
        }).outputSync();

        const libraryModuleBuildProfileModifier = new ModuleBuildProfileModifier(dir, 'library');
        libraryModuleBuildProfileModifier.addTarget({
          name: 'default',
          runtimeOS: 'HarmonyOS'
        }).outputSync();

        const projectBuildProfileModifier = new ProjectBuildProfileModifier(dir);

        // 修改deviceType default为phone 校验通过 打包成功
        projectBuildProfileModifier.updateProduct({
          'name': 'default',
          'signingConfig': 'hos',
        }).outputSync();

        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry@default',
          tasks: 'assembleHap',
        });
        const result = runHvigor(dir, commands);

        // run successful
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      },
      enableNative: true,
      runNpmInstall: true,
    });
  });
});

describe('delete.longtest', () => {
  test.concurrent('delete a component', async () => {
    await initByFAEmptyApp(async (dir) => {
      const commands = initExecuteCommand({
        mode: 'module',
        module: 'entry',
        tasks: 'assembleHap'
      });
      const result = runHvigor(dir, commands);
      expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

      const probe = new Probes(dir, resolve(dir, 'entry'));
      expect(fse.readdirSync(path.resolve(probe.getIntermediatesAssetsPath(), 'js'))).toContain('PageAbility');

      // 删除PageAbility
      const configJsonModifier = new ConfigJsonModifier(dir, 'entry');
      configJsonModifier.rmAbilityByName('.PageAbility').rmJsByName('.PageAbility').outputSync();

      // 重新build
      const buildWithoutCleanResult = runHvigor(dir, commands);
      expect(buildWithoutCleanResult.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

      expect(fse.readdirSync(path.resolve(probe.getIntermediatesAssetsPath(), 'js'))).not.toContain('PageAbility');
    });
  });
});

describe('legacy-compile-node.test', () => {
  const prepareHook = async (dir: string) => {
    const entryPackageJson = new PackageJsonModifier(dir, 'entry');
    entryPackageJson.updateDependency('library', 'file:../library');
    entryPackageJson.outputSync();

    // 更新library的main入口
    const libraryPackageJson = new PackageJsonModifier(dir, 'library');
    libraryPackageJson.updateMainField('./index.ets');
    libraryPackageJson.outputSync();

    const entryPagesIndexEtsFilePath = path.resolve(dir, 'entry/src/main/ets/MainAbility/pages/index.ets');

    // 修改entry源码,依赖library中的接口
    fs.writeFileSync(entryPagesIndexEtsFilePath, 'import hilog from \'@ohos.hilog\';\n' +
      'import testNapi from \'libentry.so\'\n' +
      'import { test } from \'library\'\n' +
      'test();\n' +
      '\n' +
      '@Entry\n' +
      '@Component\n' +
      'struct Index {\n' +
      '  @State message: string = \'Hello World\'\n' +
      '\n' +
      '  build() {\n' +
      '    Row() {\n' +
      '      Column() {\n' +
      '        Text(this.message)\n' +
      '          .fontSize(50)\n' +
      '          .fontWeight(FontWeight.Bold)\n' +
      '          .onClick(() => {\n' +
      '            hilog.isLoggable(0x0000, \'testTag\', hilog.LogLevel.INFO);\n' +
      '            hilog.info(0x0000, \'testTag\', \'Test NAPI 2 + 3 = %{public}s\', testNapi.add(2, 3));\n' +
      '          })\n' +
      '      }\n' +
      '      .width(\'100%\')\n' +
      '    }\n' +
      '    .height(\'100%\')\n' +
      '  }\n' +
      '}\n');

    const entryPagesPageEtsFilePath = path.resolve(dir, 'entry/src/main/ets/MainAbility/pages/page.ets');

    // 修改entry源码,依赖library中的页面
    fs.writeFileSync(entryPagesPageEtsFilePath, 'import { MainPage } from \'library\'\n' +
      '\n' +
      '    MainPage().build()\n' +
      '@Entry\n' +
      '@Component\n' +
      'struct Page {\n' +
      '  @State message: string = \'Hello World\'\n' +
      '\n' +
      '  /**\n' +
      '   * In low-code mode, do not add anything to the build function, as it will be\n' +
      '   * overwritten by the content generated by the .visual file in the build phase.\n' +
      '   */\n' +
      '  build() {\n' +
      '  }\n' +
      '}');
  };

  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    module: 'entry',
    tasks: 'default@LegacyCompileArkTS',
    log: 'd'
  });

  test.concurrent('test fa mode incremental compile arkTs and change library main field', async () => {
    await initByFAEmptyApp({
      prepare: prepareHook,
      cb: async (projectRoot) => {
        // 第一次执行 expect Finish
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout).toContain('runtimeOS:');
        expect(result.stdout).toContain('sdkInfo:');
        expect(result.stdout).toContain('Finished :entry:default@LegacyCompileArkTS...');
        expect(result.stdout).toContain('BUILD SUCCESSFUL');

        // 第二次执行 expect UpToDate
        const executeAgainResult = runHvigor(projectRoot, commands);
        expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyCompileArkTS...');
        expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

        // 更新library中的index.ets中的源码
        const libraryIndexEtsFilePath = path.resolve(projectRoot, 'library/index.ets');
        fs.writeFileSync(libraryIndexEtsFilePath, 'export function test() {\n' +
          '  const aa = "aa"\n' +
          '  const bb = "bb"\n' +
          '  return bb\n' +
          '}\n' +
          '\n' +
          'export { MainPage } from \'./src/main/ets/components/mainpage/MainPage\'\n');

        // 重新执行
        const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
        expect(executeAgainResultAfterDelete.stdout).toContain('Finished :entry:default@LegacyCompileArkTS...');
        expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');
      },
      runNpmInstall: true
    });
  });

  test.concurrent('test fa mode incremental compile arkTs and change library src/main', async () => {
    await initByFAEmptyApp({
      prepare: prepareHook,
      cb: async (projectRoot) => {
        // 第一次执行 expect Finish
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout).toContain('Finished :entry:default@LegacyCompileArkTS...');
        expect(result.stdout).toContain('BUILD SUCCESSFUL');

        // 第二次执行 expect UpToDate
        const executeAgainResult = runHvigor(projectRoot, commands);
        expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyCompileArkTS...');
        expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

        // 更新library中的index.ets中的源码
        const libraryIndexEtsFilePath = path.resolve(projectRoot,
          'library/src/main/ets/components/MainPage/MainPage.ets');
        fs.writeFileSync(libraryIndexEtsFilePath, 'import hilog from \'@ohos.hilog\';\n' +
          'import testNapi from \'liblibrary.so\'\n' +
          '\n' +
          '@Entry\n' +
          '@Component\n' +
          'export struct MainPage {\n' +
          '  @State message: string = \'fffffffffffffffffffffffffffffffffff\'\n' +
          '\n' +
          '  build() {\n' +
          '    Row() {\n' +
          '      Column() {\n' +
          '        Text(this.message)\n' +
          '          .fontSize(50)\n' +
          '          .fontWeight(FontWeight.Bold)\n' +
          '          .onClick(() => {\n' +
          '            hilog.isLoggable(0x0000, \'testTag\', hilog.LogLevel.INFO);\n' +
          '            hilog.info(0x0000, \'testTag\', \'Test NAPI 2 + 3 = %{public}s\', testNapi.add(2, 3));\n' +
          '          })\n' +
          '      }\n' +
          '      .width(\'100%\')\n' +
          '    }\n' +
          '    .height(\'100%\')\n' +
          '  }\n' +
          '}');

        // 重新执行
        const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
        expect(executeAgainResultAfterDelete.stdout).toContain('Finished :entry:default@LegacyCompileArkTS...');
        expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');
      },
      runNpmInstall: true
    });
  });
});

describe('make-pack-info.test', () => {
  test.concurrent('test fa mode make pack info and incremental', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry',
        tasks: 'default@LegacyMakePackInfo',
        log: 'i'
      });

      // 第一次执行 expect Finish
      const result = runHvigor(projectRoot, commands);
      expect(result.stdout).toContain('Finished :entry:default@LegacyMakePackInfo...');
      expect(result.stdout).toContain('BUILD SUCCESSFUL');

      // 第二次执行 expect UpToDate
      const executeAgainResult = runHvigor(projectRoot, commands);
      expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyMakePackInfo...');
      expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

      // 删除输出
      const outputFile = path.resolve(projectRoot, 'entry/build/default/outputs/default/pack.info');
      fse.removeSync(outputFile);

      // 删除输出第一次执行 expect Finish
      const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
      expect(executeAgainResultAfterDelete.stdout).toContain('Finished :entry:default@LegacyMakePackInfo...');
      expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');
    });
  });

  test.concurrent('test fa make pack info deviceType', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry',
        tasks: 'default@LegacyMakePackInfo',
      });
      const entryBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'entry');
      const target: ModuleTargetBuildOpt = {
        name: 'default',
        config: {
          deviceType: ['tablet']
        }
      };
      entryBuildProfile.updateTarget(target).outputSync();
      const result = runHvigor(projectRoot, commands);

      expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

      const probe = new Probes(projectRoot, resolve(projectRoot, 'entry'));
      const packinfo = fse.readJsonSync(path.resolve(probe.getModuleBuildOutputPath(), 'pack.info'));
      expect(packinfo.summary.modules[0].deviceType.length === 1).toBeTruthy();
      expect(packinfo.packages[0].deviceType.length === 1).toBeTruthy();
    });
  });

  // 测试 FA 模型修改 product bundle name 时增量失效的情形
  test.concurrent('test fa mode make pack info change product bundle name', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry',
        tasks: 'default@LegacyMakePackInfo',
        log: 'i'
      });

      // 第一次执行 expect Finish
      const result = runHvigor(projectRoot, commands);
      expect(result.stdout).toContain('Finished :entry:default@LegacyMakePackInfo...');
      expect(result.stdout).toContain('BUILD SUCCESSFUL');

      // 第二次执行 expect UpToDate
      const executeAgainResult = runHvigor(projectRoot, commands);
      expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyMakePackInfo...');
      expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

      // 更新 build-profile.json5 中的 bundleName
      const projectBuildProfileModifier = new ProjectBuildProfileModifier(projectRoot);
      projectBuildProfileModifier.updateProduct({
        name: 'default',
        bundleName: 'com.example.myapplication.test'
      }).outputSync();

      // 更新bundleName后第一次执行 expect Finish
      const updateBundleNameResult = runHvigor(projectRoot, commands);
      expect(updateBundleNameResult.stdout).toContain('Finished :entry:default@LegacyMakePackInfo...');
      expect(updateBundleNameResult.stdout).toContain('BUILD SUCCESSFUL');

      // 更新bundleName后第二次执行 expect UpToDate
      const updateBundleNameResultRunAgain = runHvigor(projectRoot, commands);
      expect(updateBundleNameResultRunAgain.stdout).toContain('UP-TO-DATE :entry:default@LegacyMakePackInfo...');
      expect(updateBundleNameResultRunAgain.stdout).toContain('BUILD SUCCESSFUL');
    });
  });

  test.concurrent('test fa mode make pack info change target', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const entryBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'entry');
      let entryTarget: ModuleTargetBuildOpt = {
        name: 'mini'
      };
      entryBuildProfile.updateTarget(entryTarget).outputSync();
      const featureBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'feature');
      const featureTarget: ModuleTargetBuildOpt = {
        name: 'mini'
      };
      featureBuildProfile.updateTarget(featureTarget).outputSync();
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        tasks: 'assembleHap'
      });
      const result = runHvigor(projectRoot, commands);

      expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      entryTarget = {
        name: 'mini',
        config: {
          deviceType: ['tablet']
        }
      };
      entryBuildProfile.updateTarget(entryTarget).outputSync();
      const result2 = runHvigor(projectRoot, commands);

      expect(result2.stdout.includes('Finished :feature:mini@LegacyMakePackInfo')).toBeTruthy();
      entryBuildProfile.removeLastTarget().outputSync();
      runShouldFail(projectRoot, commands, 'Make sure module \'feature\' has the same target as module \'entry\'.');
    });
  });
});


describe('make-project-pack-info.test', () => {
  test.concurrent('test fa mode make project pack.info and incremental', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'project',
        product: 'default',
        tasks: 'MakeProjectPackInfo',
        log: 'i'
      });

      // 第一次执行 expect Finish
      const result = runHvigor(projectRoot, commands);
      expect(result.stdout).toContain('Finished ::MakeProjectPackInfo...');
      expect(result.stdout).toContain('BUILD SUCCESSFUL');

      // 第二次执行 expect UpToDate
      const executeAgainResult = runHvigor(projectRoot, commands);
      expect(executeAgainResult.stdout).toContain('UP-TO-DATE ::MakeProjectPackInfo...');
      expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

      // 删除输出
      const outputFile = path.resolve(projectRoot, 'entry/build/default/outputs/default/pack.info');
      fse.removeSync(outputFile);

      // 删除输出第一次执行 expect Finish
      const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
      expect(executeAgainResultAfterDelete.stdout).toContain('Finished ::MakeProjectPackInfo...');
      expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');
    });
  });
});

describe('legacy-pre-build.test', () => {
  test.concurrent('Fa is do schema check.', async () => {
    await initByFAEmptyApp({
      cb: async (dir) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry@default',
          tasks: 'default@LegacyPreBuild',

        });
        const projectBuildProfilePath = path.resolve(dir, 'build-profile.json5');
        const moduleBuildProfilePath = path.resolve(dir, 'entry', 'build-profile.json5');
        const configJsonPath = path.resolve(dir, 'entry', 'src', 'main', 'config.json');
        const result = runHvigor(dir, commands, { stderr: 'pipe' });
        expect(result.stdout).toContain(`Schema ${projectBuildProfilePath} success`);
        expect(result.stdout).toContain(`Schema ${moduleBuildProfilePath} success`);
        expect(result.stdout).toContain(`Schema ${configJsonPath} success`);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }
    });
  });

  test.concurrent('ets compatible version', async () => {
    await initByFAEmptyApp({
      cb: async (dir) => {
        const projectBuildProfileModifier = new ProjectBuildProfileModifier(dir);
        projectBuildProfileModifier.setCompatibleSdkVersion(6).outputSync();
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry',
          tasks: 'default@LegacyPreBuild',
        });
        try {
          runHvigor(dir, commands, { stderr: 'pipe' });
        } catch (e: any) {
          expect(e.stderr).toContain('The minimum compatible version of ArkTS is 7.');
        }
      }
    });
  });
});

describe('basic.longtest.ts', () => {
  describe('FA App', () => {
    test.concurrent('test fa build-profile.json5 module sequence', async () => {
      await initByFAEmptyApp(async (dir) => {
        const buildProfileJson5Path = path.resolve(dir, 'build-profile.json5');
        const json5Obj = Json5Reader.getJson5Obj(buildProfileJson5Path);
        fse.writeFileSync(buildProfileJson5Path, JSON.stringify({
          ...json5Obj,
          'modules': [
            {
              'name': 'feature',
              'srcPath': './feature',
              'targets': [
                {
                  'name': 'default',
                  'applyToProducts': [
                    'default'
                  ]
                }
              ]
            },
            {
              'name': 'library',
              'srcPath': './library'
            },
            {
              'name': 'library1',
              'srcPath': './library1'
            },
            {
              'name': 'entry',
              'srcPath': './entry',
              'targets': [
                {
                  'name': 'default',
                  'applyToProducts': [
                    'default'
                  ]
                }
              ]
            }
          ]
        }));

        const commands = initExecuteCommand({
          product: 'default',
          tasks: 'assembleHap'
        });
        const result = runHvigor(dir, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('FA app assembleHap with target config', async () => {
      await initByFAEmptyApp({
        prepare: async (dir) => {
          await fse.copy(path.resolve(appRoot(), 'test/resources/extra/target'), dir);
        },
        cb: async (dir) => {
          const commands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            tasks: 'assembleHap'
          });

          new ProjectBuildProfileModifier(dir).setSdkVersion(9).setCompatibleSdkVersion(9).outputSync();
          const result = runHvigor(dir, commands);
          expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
          const probe = new Probes(dir, resolve(dir, 'entry'));
          const buildOutput = readdirSync(probe.getModuleBuildOutputPath());
          expect(buildOutput).toEqual([
            'entry-default-unsigned.hap',
            'entry-default-signed.hap',
            'pack.info'
          ].sort());

          // 保证编译产物只有page/index2
          const pagesDir = path.resolve(probe.getIntermediatesAssetsPath(), 'js', 'MainAbility', 'pages');
          const pagesOutput = readdirSync(pagesDir);
          expect(pagesOutput).toContain('page.abc');
          expect(pagesOutput).not.toContain('index.abc');

          // 保证manifest.json的MainAbility中的pages为index2
          const manifestJsonOpt = Json5Reader.getJson5Obj(
            path.resolve(probe.getIntermediatesManifest(), 'MainAbility', 'manifest.json'));
          expect(manifestJsonOpt.pages).toEqual(['pages/page']);
        }
      });
    });

    test.concurrent('FA app assembleApp', async () => {
      await initByFAEmptyApp(async (dir) => {
        const commands = initExecuteCommand({
          mode: 'project',
          product: 'default',
          tasks: 'assembleApp'
        });
        const result = runHvigor(dir, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const probe = new Probes(dir, resolve(dir, 'entry'));
        const buildOutput = readdirSync(probe.getProjectOutputPath());
        expect(buildOutput).toEqual([
          `${basename(dir)}-default-unsigned.app`,
          `${basename(dir)}-default-signed.app`,
          'pack.info',
          'pack.res'
        ].sort());
      });
    });

    test.concurrent('FA app assembleHap', async () => {
      await initByFAEmptyApp(async (dir) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          tasks: 'assembleHap'
        });
        const result = runHvigor(dir, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const probe = new Probes(dir, resolve(dir, 'entry'));
        const buildOutput = readdirSync(probe.getModuleBuildOutputPath());
        expect(buildOutput).toEqual([
          'entry-default-unsigned.hap',
          'entry-default-signed.hap',
          'pack.info'
        ].sort());
      });
    });
  });

  describe('FA Service', () => {
    test.concurrent('FA service test ohosTest pack.info', async () => {
      await initByFAEmptyService({
        prepare: addOhosTestDependency,
        cb: async (dir) => {
          const commands = initExecuteCommand({
            mode: 'module',
            module: 'entry@ohosTest',
            tasks: 'assembleHap'
          });
          const result = await runHvigor(dir, commands);
          expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

          const probe = new Probes(dir, resolve(dir, 'entry'));

          if (!fse.existsSync(path.resolve(dir, 'entry', 'src', 'ohosTest'))) {
            return;
          }

          const packinfo = fse.readJsonSync(path.resolve(probe.getModuleBuildOutputPath(),
            '..', 'ohosTest', 'pack.info'));
          expect(packinfo.summary.modules.length).toEqual(2);
        },
        runNpmInstall: true
      });
    });
  });
});

describe('build-app-sign.longtest', () => {
  test.concurrent('test fa app build app not signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'project',
        product: 'default',
        tasks: 'assembleApp'
      });
      await testSignFalse(projectRoot, commands);
    }, { sign: false });
  });

  test.concurrent('test fa app build app signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'project',
        product: 'default',
        tasks: 'assembleApp'
      });
      await testSignTrue(projectRoot, commands);
    }, { sign: true });
  });

  test.concurrent('test fa service build app not signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'project',
        product: 'default',
        tasks: 'assembleApp'
      });
      await testSignFalse(projectRoot, commands);
    }, { sign: false });
  });

  test.concurrent('test fa service build app signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'project',
        product: 'default',
        tasks: 'assembleApp'
      });
      await testSignTrue(projectRoot, commands);
    }, { sign: true });
  });
});

describe('build-hap-sign.longtest', () => {
  test.concurrent('test fa app package entry hap not signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'assembleHap'
      });
      await testSignFalse(projectRoot, commands);
    }, { sign: false });
  });

  test.concurrent('test fa app package entry hap signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'assembleHap'
      });
      await testSignTrue(projectRoot, commands);
    }, { sign: true });
  });

  test.concurrent('test fa app package feature hap not signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'feature@default',
        tasks: 'assembleHap'
      });
      await testHapSignFalse(projectRoot, commands, 'feature');
    }, { sign: false });
  });

  test.concurrent('test fa app package feature hap signing', async () => {
    await initByFAEmptyApp(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'feature@default',
        tasks: 'assembleHap'
      });
      await testHapSignTrue(projectRoot, commands, 'feature');
    }, { sign: true });
  });

  test.concurrent('test fa service package entry hap not signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'assembleHap'
      });
      await testSignFalse(projectRoot, commands);
    }, { sign: false });
  });

  test.concurrent('test fa service package entry hap signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'entry@default',
        tasks: 'assembleHap'
      });
      await testSignTrue(projectRoot, commands);
    }, { sign: true });
  });

  test.concurrent('test fa service package feature hap not signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'feature@default',
        tasks: 'assembleHap'
      });
      await testHapSignFalse(projectRoot, commands, 'feature');
    }, { sign: false });
  });

  test.concurrent('test fa service package feature hap signing', async () => {
    await initByFAEmptyService(async (projectRoot) => {
      const commands = initExecuteCommand({
        mode: 'module',
        product: 'default',
        module: 'feature@default',
        tasks: 'assembleHap'
      });
      await testHapSignTrue(projectRoot, commands, 'feature');
    }, { sign: true });
  });
});

describe('compile-resource.test', () => {
  const legacyHookCompileResourceTask = 'default@LegacyHookCompileResource';
  const compileShellResourceTask = 'default@CompileShellResource';
  const compileResInc = 'compileResInc=true';

  describe('test cpmpile resource', () => {
    test.concurrent('test fa app entry compile resource', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          module: 'entry',
          product: 'default',
          tasks: 'default@LegacyHookCompileResource'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const configPath = path.resolve(projectRoot,
          'entry/build/default/intermediates/res/default/resConfig.json');
        expect(fs.existsSync(configPath)).toBeTruthy();
      });
    });

    test.concurrent('test fa app feature compile resource', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          module: 'feature',
          product: 'default',
          tasks: 'default@LegacyHookCompileResource'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const configPath = path.resolve(projectRoot,
          'feature/build/default/intermediates/res/default/entry/resConfig.json');
        expect(fs.existsSync(configPath)).toBeTruthy();
      });
    });

    test.concurrent('test fa service compile resource', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          module: 'entry',
          product: 'default',
          tasks: legacyHookCompileResourceTask
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test feature compile resource should contain same target in entry', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const featureBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'feature');
        featureBuildProfile.addTarget({
          name: 'featureTarget'
        }).outputSync();

        const commands = initExecuteCommand({
          mode: 'module',
          module: 'feature',
          product: 'default',
          tasks: legacyHookCompileResourceTask
        });
        runShouldFail(projectRoot, commands, 'Module feature must has the same target as entry. ' +
          'Current target \'featureTarget\' is not found in module entry.');
      });
    });

    test.concurrent('test feature and entry module should has same deviceType', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const featureBuildProfile = new ConfigJsonModifier(projectRoot, 'feature');
        featureBuildProfile.setDeviceType(['tv']).outputSync();

        const commands = initExecuteCommand({
          mode: 'module',
          module: 'feature',
          product: 'default',
          tasks: legacyHookCompileResourceTask
        });
        runShouldFail(projectRoot, commands,
          'feature and entry do not have the same device type. The feature hap is invalid.');
      });
    });

    test.concurrent('test target should has same deviceType', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          module: 'feature',
          product: 'default',
          tasks: legacyHookCompileResourceTask
        });
        await copy(path.resolve(appRoot(), 'test/resources/extra/target'), projectRoot);

        runShouldFail(projectRoot, commands,
          'feature and entry do not have the same device type. The feature hap is invalid.');
      });
    });

    // Restool增量方式资源编译
    describe('test increment compile resources', () => {
      test.concurrent('test fa service increment compile resource', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          const commands = initExecuteCommand({
            mode: 'module',
            module: 'entry',
            product: 'default',
            tasks: legacyHookCompileResourceTask,
            compileResInc: compileResInc
          });
          const result = runHvigor(projectRoot, commands);
          expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        });
      });

      test.concurrent('test feature increment compile resource should contain same target in entry', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          const featureBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'feature');
          featureBuildProfile.addTarget({
            name: 'featureTarget'
          }).outputSync();

          const commands = initExecuteCommand({
            mode: 'module',
            module: 'feature',
            product: 'default',
            tasks: legacyHookCompileResourceTask,
            compileResInc: compileResInc
          });
          runShouldFail(projectRoot, commands, 'Module feature must has the same target as entry. ' +
            'Current target \'featureTarget\' is not found in module entry.');
        });
      });

      test.concurrent('test feature and entry module should has same deviceType', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          const featureBuildProfile = new ConfigJsonModifier(projectRoot, 'feature');
          featureBuildProfile.setDeviceType(['tv']).outputSync();

          const commands = initExecuteCommand({
            mode: 'module',
            module: 'feature',
            product: 'default',
            tasks: legacyHookCompileResourceTask,
            compileResInc: compileResInc
          });
          runShouldFail(projectRoot, commands,
            'feature and entry do not have the same device type. The feature hap is invalid.');
        });
      });

      test.concurrent('test target should has same deviceType', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          const commands = initExecuteCommand({
            mode: 'module',
            module: 'feature',
            product: 'default',
            tasks: legacyHookCompileResourceTask,
            compileResInc: compileResInc
          });
          await fse.copy(path.resolve(appRoot(), 'test/resources/extra/target'), projectRoot);

          runShouldFail(projectRoot, commands,
            'feature and entry do not have the same device type. The feature hap is invalid.');
        });
      });

      // FA模型 feature依赖entry icon
      test.concurrent('test feature compile resource Depended Entry Resource', async () => {
        await initByFAEmptyApp(async (projectRoot) => {
          const commands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            tasks: 'assembleHap',
          });

          const projectBuildProfileModifier = new ProjectBuildProfileModifier(projectRoot);
          projectBuildProfileModifier.setCompatibleSdkVersion(9);
          projectBuildProfileModifier.setSdkVersion(9);
          projectBuildProfileModifier.outputSync();

          const result = runHvigor(projectRoot, commands, { stderr: 'pipe' });
          expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

          const probes = new Probes(projectRoot, resolve(projectRoot, 'feature'));
          const media = resolve(probes.getIntermediatesRes(), 'entry/resources/base/media');
          const files: string[] = fse.readdirSync(media);
          expect(files.includes('icon_entry.png')).toBeTruthy();
        });
      });
    });
  });

  describe('generate-pack-res.test', function () {
    test.concurrent('test show in service center fa app GeneratePackRes', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'project',
          product: 'default',
          tasks: 'GeneratePackRes'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished ::GeneratePackRes')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE ::GeneratePackRes')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'build/outputs/default/pack.res');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished ::GeneratePackRes')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test show in service center fa service GeneratePackRes', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'project',
          product: 'default',
          tasks: 'GeneratePackRes'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished ::GeneratePackRes')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE ::GeneratePackRes')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'build/outputs/default/pack.res');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished ::GeneratePackRes')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }, { sign: false });
    });
  });

  describe('generate-loader-json.test', function () {
    test.concurrent('test fa mode generate loader json and incremental', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry',
          tasks: 'default@LegacyGenerateLoaderJson',
          log: 'i'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout).toContain('Finished :entry:default@LegacyGenerateLoaderJson...');
        expect(result.stdout).toContain('BUILD SUCCESSFUL');

        const executeAgainResult = runHvigor(projectRoot, commands);
        expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyGenerateLoaderJson...');
        expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');

        const loaderOutputFile = path.resolve(projectRoot,
          'entry/build/default/intermediates/loader/default/MainAbility/loader.json');
        fse.removeSync(loaderOutputFile);

        const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
        expect(executeAgainResultAfterDelete.stdout).toContain('Finished :entry:default@LegacyGenerateLoaderJson...');
        expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');

        const moduleBuildProfileModifier = new ModuleBuildProfileModifier(projectRoot, 'entry');
        moduleBuildProfileModifier.updateTarget({
          name: 'default', source: {
            abilities: [
              {
                name: '.MainAbility',
                pages: ['page/page']
              }
            ]
          }
        }).outputSync();
        const buildAgain = runHvigor(projectRoot, commands);

        // 不能up-to-date
        expect(buildAgain.stdout.includes('Finished :entry:default@LegacyGenerateLoaderJson')).toBeTruthy();
        expect(buildAgain.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const probes = new Probes(projectRoot, resolve(projectRoot, 'entry'));
        const loaderJsonPath = resolve(probes.getIntermediatesLoaderPath(), 'MainAbility', 'loader.json');
        const targetPages = {
          pages: [
            'page/page'
          ]
        };
        expect(JSON.stringify(getJson5Obj(loaderJsonPath))).toContain(JSON.stringify(targetPages));
      });
    });
  });

  describe('legacy-merge-profile.test', () => {
    const LOG: OhosLogger = OhosLogger.getLogger('legacy-merge-profile.OPENLAB_AIDE_ZOS_Build_product_target_06_004.ts');

    async function testLegacyMergeProfile(projectRoot: string) {
      const commands = initExecuteCommand({
        mode: 'module',
        module: 'entry',
        product: 'default',
        tasks: 'default@LegacyMergeProfile'
      });

      // 第一次执行任务
      const result1 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result1.exitCode).toEqual(0);
      expect(result1.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result1.stdout.includes('Finished :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 没有修改，第二次执行任务
      const result2 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result2.exitCode).toEqual(0);
      expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result2.stdout.includes('UP-TO-DATE :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 修改module.json5，第三次执行任务
      const entryConfigJsonPath = path.resolve(projectRoot, 'entry', 'src', 'main', 'config.json');
      const entryConfigJson = parseJsonText(fse.readFileSync(entryConfigJsonPath).toString()) as ConfigOptObj;
      entryConfigJson.module.distro.deliveryWithInstall = false;
      fse.writeFileSync(entryConfigJsonPath, JSON.stringify(entryConfigJson));
      const result3 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result3.exitCode).toEqual(0);
      expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result3.stdout.includes('Finished :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 没有修改，第四次执行任务
      const result4 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result4.exitCode).toEqual(0);
      expect(result4.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result4.stdout.includes('UP-TO-DATE :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 修改product的bundleName，第五次执行任务
      const buildJsonPath = path.resolve(projectRoot, 'build-profile.json5');
      const buildJson = parseJsonText(fse.readFileSync(buildJsonPath).toString()) as ProjectProfileOpt;
      LOG.info('buildJson.app.products:', buildJson.app.products);
      if (buildJson.app.products) {
        buildJson.app.products[0].bundleName = 'com.example.myapplication1';
        fse.writeFileSync(buildJsonPath, JSON.stringify(buildJson));
        const result5 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
        expect(result5.exitCode).toEqual(0);
        expect(result5.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        expect(result5.stdout.includes('Finished :entry:default@LegacyMergeProfile')).toBeTruthy();
      }

      // 修改buildRoot，第6次执行任务
      commands.push('-p');
      commands.push('buildRoot=build1');
      const result6 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result6.exitCode).toEqual(0);
      expect(result6.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result6.stdout.includes('Finished :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 修改debuggable，第7次执行任务
      commands.push('-p');
      commands.push('debuggable=false');
      const result7 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result7.exitCode).toEqual(0);
      expect(result7.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result7.stdout.includes('Finished :entry:default@LegacyMergeProfile')).toBeTruthy();

      // 没有修改，第8次执行任务
      const result8 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result8.exitCode).toEqual(0);
      expect(result8.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result8.stdout.includes('UP-TO-DATE :entry:default@LegacyMergeProfile')).toBeTruthy();
    }

    test.concurrent('test fa app', async () => {
      await initByFAEmptyApp(testLegacyMergeProfile);
    });

    test.concurrent('test fa Service', async () => {
      await initByFAEmptyService(testLegacyMergeProfile);
    });
  });

  describe('legacy-process-profile.test', function () {
    describe('test legacy process profile', () => {

      test.concurrent('test target should configure valid pages', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          await fse.copy(path.resolve(appRoot(), 'test/resources/extra/target'), projectRoot);

          const featureBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'feature');
          await featureBuildProfile.updateTarget({
            name: 'default2',
            source: {
              abilities: [
                {
                  name: '.MainAbility',
                  pages: [
                    'page9'
                  ]
                }
              ]
            }
          }).output();
          const commands = initExecuteCommand({
            mode: 'module',
            module: 'feature',
            product: 'default2',
            tasks: 'default2@LegacyProcessProfile'
          });

          runShouldFail(projectRoot, commands, ' Check page9 in build-profile.json5 configured in config.json.');
        });
      });

      test.concurrent('test target should configure valid ability', async () => {
        await initByFAEmptyService(async (projectRoot) => {
          await fse.copy(path.resolve(appRoot(), 'test/resources/extra/target'), projectRoot);

          const featureBuildProfile = new ModuleBuildProfileModifier(projectRoot, 'feature');
          await featureBuildProfile.updateTarget({
            name: 'default2',
            source: {
              abilities: [
                {
                  name: '.ab3%dAbility',
                }
              ]
            }
          }).output();
          const commands = initExecuteCommand({
            mode: 'module',
            module: 'feature',
            product: 'default2',
            tasks: 'default2@LegacyProcessProfile'
          });

          runShouldFail(projectRoot, commands,
            'Check .ab3%dAbility in build-profile.json5 configured in config.json.');
        });
      });
    });

    async function testLegacyProcessProfile(projectRoot: string) {
      const commands = initExecuteCommand({
        mode: 'module',
        module: 'entry',
        product: 'default',
        tasks: 'default@LegacyProcessProfile'
      });

      // 第一次执行任务
      const result1 = runHvigor(projectRoot, commands);
      expect(result1.exitCode).toEqual(0);
      expect(result1.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result1.stdout).toContain('restool module names: entry,feature,library,library1');
      expect(result1.stdout).toContain('restool module names: feature,entry,library,library1');
      expect(result1.stdout).toContain('restool module names: library,entry,feature,library1');
      expect(result1.stdout).toContain('restool module names: library1,entry,feature,library');
      expect(result1.stdout.includes('Finished :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 没有修改，第二次执行任务
      const result2 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result2.exitCode).toEqual(0);
      expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result2.stdout.includes('UP-TO-DATE :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 修改module.json5，第三次执行任务
      const entryBuildJsonPath = path.resolve(projectRoot, 'entry', 'build-profile.json5');
      const entryBuildJson = parseJsonText(fse.readFileSync(entryBuildJsonPath).toString()) as ModuleBuildOpt;
      if (entryBuildJson.targets && entryBuildJson.targets.length > 0) {
        if (entryBuildJson.targets[0].config) {
          if (entryBuildJson.targets[0].config.deviceType) {
            entryBuildJson.targets[0].config.deviceType.push('tablet');
          } else {
            entryBuildJson.targets[0].config.deviceType = ['tablet'];
          }
        } else {
          entryBuildJson.targets[0].config = { deviceType: ['tablet'] };
        }
      }
      fse.writeFileSync(entryBuildJsonPath, JSON.stringify(entryBuildJson));
      const result3 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result3.exitCode).toEqual(0);
      expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result3.stdout).toContain('restool module names: entry,feature,library,library1');
      expect(result3.stdout).toContain('restool module names: feature,entry,library,library1');
      expect(result3.stdout).toContain('restool module names: library,entry,feature,library1');
      expect(result3.stdout).toContain('restool module names: library1,entry,feature,library');
      expect(result3.stdout.includes('Finished :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 没有修改，第四次执行任务
      const result4 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result4.exitCode).toEqual(0);
      expect(result4.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result4.stdout.includes('UP-TO-DATE :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 修改product的bundleName，第五次执行任务
      if (entryBuildJson.targets) {
        entryBuildJson.targets[0].source = {
          abilities: [
            {
              name: '.MainAbility',
              pages: [
                'pages/page'
              ]
            }
          ]
        };
      }
      fse.writeFileSync(entryBuildJsonPath, JSON.stringify(entryBuildJson));
      const result5 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result5.exitCode).toEqual(0);
      expect(result5.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result5.stdout.includes('Finished :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 修改buildRoot，影响前置任务LegacyMergeProfile重新执行，第6次执行任务
      commands.push('-p');
      commands.push('buildRoot=build1');
      const result6 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result6.exitCode).toEqual(0);
      expect(result6.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result6.stdout.includes('Finished :entry:default@LegacyProcessProfile')).toBeTruthy();

      // 没有修改，第8次执行任务
      const result7 = runHvigor(projectRoot, commands, { stderr: 'pipe' });
      expect(result7.exitCode).toEqual(0);
      expect(result7.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      expect(result7.stdout.includes('UP-TO-DATE :entry:default@LegacyProcessProfile')).toBeTruthy();
    }

    describe('test legacy process profile Incremental', () => {
      test.concurrent('test fa app', async () => {
        await initByFAEmptyApp(testLegacyProcessProfile);
      });

      test.concurrent('test fa Service', async () => {
        await initByFAEmptyService(testLegacyProcessProfile);
      });
    });
  });

  describe('ohos-test.test', function () {
    test.concurrent('test ohosTest - FA assembleHap', async () => {
      await initByFAEmptyApp({
        prepare: addOhosTestDependency,
        cb: async (dir) => {
          const commands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@ohosTest',
            tasks: 'assembleHap',
          });
          const result = runHvigor(dir, commands);

          if (!fse.existsSync(path.resolve(dir, 'entry', 'src', 'ohosTest'))) {
            return;
          }

          // run successful
          expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
          const probe = new Probes(dir, resolve(dir, 'entry'), 'ohosTest');
          const faIntermediateRes = readdirSync(probe.getIntermediatesRes());
          expect(faIntermediateRes.sort()).toEqual([
            'resources',
            'config.json',
            'resConfig.json',
            'resources.index',
            'ResourceTable.txt'
          ].sort());

          const faBuildOutput = readdirSync(probe.getModuleBuildOutputPath());
          expect(faBuildOutput.sort()).toEqual([
            'entry-ohosTest-unsigned.hap',
            'entry-ohosTest-signed.hap',
            'pack.info'
          ].sort());
        },
        runNpmInstall: true
      });
    });

    test.concurrent('test ohosTest - FA increment', async () => {
      await initByFAEmptyApp({
        prepare: addOhosTestDependency,
        cb: async (dir) => {
          const defaultCommands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
          });
          const defaultResult = runHvigor(dir, defaultCommands);
          expect(defaultResult.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

          const ohosTestCommands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@ohosTest',
            tasks: 'assembleHap',
          });
          const ohosTestResult = runHvigor(dir, ohosTestCommands);

          expect(fse.existsSync(path.resolve(dir, 'entry', 'src', 'ohosTest'))).toBeTruthy();

          // run successful
          expect(ohosTestResult.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
          const probe = new Probes(dir, resolve(dir, 'entry'), 'ohosTest');
          const faIntermediateRes = readdirSync(probe.getIntermediatesRes());
          expect(faIntermediateRes.sort()).toEqual([
            'resources',
            'config.json',
            'resConfig.json',
            'resources.index',
            'ResourceTable.txt'
          ].sort());

          const faBuildOutput = readdirSync(probe.getModuleBuildOutputPath());
          expect(faBuildOutput.sort()).toEqual([
            'entry-ohosTest-unsigned.hap',
            'entry-ohosTest-signed.hap',
            'pack.info'
          ].sort());
          const firstPackOTHapTime = fse.statSync(probe.getModuleBuildOutputPath()).mtimeMs;

          // add test codes and re-run
          fse.copySync(resolve(appRoot(), 'test/resources/extra/ohos-test'), resolve(dir, 'entry/src/ohosTest/ets/test'));
          runHvigor(dir, ohosTestCommands);
          runHvigor(dir, ohosTestCommands);

          const secondPackOTHapTime = fse.statSync(probe.getModuleBuildOutputPath()).mtimeMs;
          expect(firstPackOTHapTime === secondPackOTHapTime).toBeFalsy();
        },
        runNpmInstall: true
      });
    });
  });

  describe('package-app.longtest', function () {
    test.concurrent('test fa app package app', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'project',
          product: 'default',
          tasks: 'PackageApp'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('project has submodules:entry,feature,library,library1')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'build/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('project has submodules:entry,feature,library,library1')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test fa service package app', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'project',
          product: 'default',
          tasks: 'PackageApp'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('project has submodules:entry,feature,library,library1')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'build/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished ::PackageApp')).toBeTruthy();
        expect(result.stdout.includes('project has submodules:entry,feature,library,library1')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }, { sign: false });
    });
  });

  describe('package-hap.longtest', function () {
    test.concurrent('test fa app package entry hap', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry@default',
          tasks: 'default@LegacyPackageHap'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'entry/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test fa app package feature hap', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'feature@default',
          tasks: 'default@LegacyPackageHap'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'feature/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test fa service package entry hap', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry@default',
          tasks: 'default@LegacyPackageHap'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'entry/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :entry:default@LegacyPackageHap')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }, { sign: false });
    });

    test.concurrent('test fa service package feature hap', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'feature@default',
          tasks: 'default@LegacyPackageHap'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'feature/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :feature:default@LegacyPackageHap')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }, { sign: false });
    });
  });

  describe('package-har.test', function () {
    test.concurrent('test fa app package har', async () => {
      await initByFAEmptyApp(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'library@default',
          tasks: 'default@LegacyPackageHar'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'library/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      });
    });

    test.concurrent('test fa service package har', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'library@default',
          tasks: 'default@LegacyPackageHar'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout.includes('Finished :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const result2 = runHvigor(projectRoot, commands);
        expect(result2.stdout.includes('UP-TO-DATE :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result2.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
        const output = path.resolve(projectRoot, 'library/build/default/outputs');
        fse.removeSync(output);
        const result3 = runHvigor(projectRoot, commands);
        expect(result3.stdout.includes('Finished :library:default@LegacyPackageHar')).toBeTruthy();
        expect(result3.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();
      }, { sign: false });
    });
  });

  describe('previewer.test', function () {
    describe('test Preview FA app', () => {
      test.concurrent('fa app entry preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          const moduleName = 'entry';
          const buildConfig = generateDefaultFABuildConfig(projectDir, moduleName);
          testPreview(projectDir, moduleName, 'page', buildConfig);
        });
      });

      test.concurrent('fa app feature preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          const moduleName = 'etsfeature';
          const buildConfig = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.appResource =
            path.resolve(projectDir, moduleName, '.preview/default/intermediates/res/default/entry/ResourceTable.txt');
          buildConfig.stageRouterConfig.paths[0] =
            path.resolve(projectDir, moduleName, '.preview/default/intermediates/res/default/entry/module.json');
          testPreview(projectDir, moduleName, 'page', buildConfig);
        });
      });

      test.concurrent('fa app library preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          const moduleName = 'library';
          const buildConfig: any = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.aceCompileMode = 'npm';
          buildConfig.aceManifestPath = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/manifest/default/manifest.json');
          buildConfig.stageRouterConfig.contents[1] = '{"src":["components/MainPage/MainPage"]}';
          buildConfig.aceBuildJson = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/loader/default/loader.json');
          buildConfig.aceModuleRoot = path.resolve(projectDir, moduleName, 'src/main/ets');
          buildConfig.aceModuleBuild =
            path.resolve(projectDir, moduleName, '.preview/intermediates/res/debug/rich/assets/js');
          buildConfig.aceSuperVisualPath = path.resolve(projectDir, moduleName, 'src/main/supervisual');
          testPreview(projectDir, moduleName, 'page', buildConfig,
            ['previewer.replace.page=components/MainPage/MainPage']);
        });
      });

      test.concurrent('fa app vlibrary preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          const moduleName = 'vlibrary';
          const buildConfig: any = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.aceCompileMode = 'npm';
          buildConfig.aceManifestPath = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/manifest/default/manifest.json');
          buildConfig.stageRouterConfig.contents[1] = '{"src":["components/MainPage/MainPage"]}';
          buildConfig.aceBuildJson = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/loader/default/loader.json');
          buildConfig.aceModuleRoot = path.resolve(projectDir, moduleName, 'src/main/ets');
          buildConfig.aceModuleBuild =
            path.resolve(projectDir, moduleName, '.preview/intermediates/res/debug/rich/assets/js');
          buildConfig.aceSuperVisualPath = path.resolve(projectDir, moduleName, 'src/main/supervisual');
          testPreview(projectDir, moduleName, 'page', buildConfig,
            ['previewer.replace.page=components/MainPage/MainPage']);
        });
      });
    });

    describe('test Preview FA service', () => {
      test.concurrent('fa service entry preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          const moduleName = 'entry';
          const buildConfig = generateDefaultFABuildConfig(projectDir, moduleName);
          testPreview(projectDir, 'entry', 'page', buildConfig);
        });
      });

      test.concurrent('fa service feature preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          const moduleName = 'etsfeature';
          const buildConfig = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.appResource =
            path.resolve(projectDir, moduleName, '.preview/default/intermediates/res/default/entry/ResourceTable.txt');
          buildConfig.stageRouterConfig.paths[0] =
            path.resolve(projectDir, moduleName, '.preview/default/intermediates/res/default/entry/module.json');
          testPreview(projectDir, moduleName, 'page', buildConfig);
        });
      });

      test.concurrent('fa service library preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          const moduleName = 'library';
          const buildConfig: any = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.aceCompileMode = 'npm';
          buildConfig.aceManifestPath = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/manifest/default/manifest.json');
          buildConfig.stageRouterConfig.contents[1] = '{"src":["components/MainPage/MainPage"]}';
          buildConfig.aceBuildJson = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/loader/default/loader.json');
          buildConfig.aceModuleRoot = path.resolve(projectDir, moduleName, 'src/main/ets');
          buildConfig.aceModuleBuild =
            path.resolve(projectDir, moduleName, '.preview/intermediates/res/debug/rich/assets/js');
          buildConfig.aceSuperVisualPath = path.resolve(projectDir, moduleName, 'src/main/supervisual');
          testPreview(projectDir, moduleName, 'page', buildConfig,
            ['previewer.replace.page=components/MainPage/MainPage']);
        });
      });

      test.concurrent('fa service vlibrary preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          const moduleName = 'vlibrary';
          const buildConfig: any = generateDefaultFABuildConfig(projectDir, moduleName);
          buildConfig.aceCompileMode = 'npm';
          buildConfig.aceManifestPath = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/manifest/default/manifest.json');
          buildConfig.stageRouterConfig.contents[1] = '{"src":["components/MainPage/MainPage"]}';
          buildConfig.aceBuildJson = path.resolve(
            projectDir, moduleName, '.preview/default/intermediates/loader/default/loader.json');
          buildConfig.aceModuleRoot = path.resolve(projectDir, moduleName, 'src/main/ets');
          buildConfig.aceModuleBuild =
            path.resolve(projectDir, moduleName, '.preview/intermediates/res/debug/rich/assets/js');
          buildConfig.aceSuperVisualPath = path.resolve(projectDir, moduleName, 'src/main/supervisual');
          testPreview(projectDir, 'vlibrary', 'page', buildConfig,
            ['previewer.replace.page=components/MainPage/MainPage']);
        });
      });
    });
  });

  describe('previewer-increment.test', function () {
    // 增量方式预览参数
    const compileResIncParam = 'compileResInc=true';

    describe('test Preview FA app', () => {
      test.concurrent('fa app entry preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          testPreviewResource(projectDir, 'entry',
            ['previewer.replace.srcPath=PageAbility', 'previewer.replace.page=pages/index', compileResIncParam]);
        });
      });

      test.concurrent('fa app feature preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          testPreviewResource(projectDir, 'feature', [compileResIncParam]);
        });
      });

      test.concurrent('fa app library preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          testPreviewResource(projectDir, 'library', [compileResIncParam]);
        });
      });

      test.concurrent('fa app library1 preview', async () => {
        await initByFAEmptyApp(async (projectDir) => {
          testPreviewResource(projectDir, 'library1', [compileResIncParam]);
        });
      });
    });

    describe('test Preview FA service', () => {
      test.concurrent('fa service entry preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          testPreviewResource(projectDir, 'entry', [compileResIncParam]);
        });
      });

      test.concurrent('fa service feature preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          testPreviewResource(projectDir, 'feature', [compileResIncParam]);
        });
      });

      test.concurrent('fa service library preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          testPreviewResource(projectDir, 'library', [compileResIncParam]);
        });
      });

      test.concurrent('fa service library1 preview', async () => {
        await initByFAEmptyService(async (projectDir) => {
          testPreviewResource(projectDir, 'library1', [compileResIncParam]);
        });
      });
    });
  });

  describe('process-libs.longtest', function () {
    test.concurrent('test FA app', async () => {
      await initByFAEmptyApp({
        cb: testProcessLibs,
        prepare: prepareProcessLibs,
        enableNative: true,
        runNpmInstall: true
      });
    });

    test.concurrent('test FA Service', async () => {
      await initByFAEmptyService({
        cb: testProcessLibs,
        prepare: prepareProcessLibs,
        enableNative: true,
        runNpmInstall: true
      });
    });
  });

  describe('process-resource.test', function () {
    test.concurrent('test fa mode process resource and incremental', async () => {
      await initByFAEmptyService(async (projectRoot) => {
        const commands = initExecuteCommand({
          mode: 'module',
          product: 'default',
          module: 'entry',
          tasks: 'default@LegacyProcessResource',
          log: 'd'
        });
        const result = runHvigor(projectRoot, commands);
        expect(result.stdout).toContain('Finished :entry:default@LegacyProcessResource...');
        expect(result.stdout).toContain('restool module names: entry,feature,library,library1');
        expect(result.stdout).toContain('restool module names: feature,entry,library,library1');
        expect(result.stdout).toContain('restool module names: library,entry,feature,library1');
        expect(result.stdout).toContain('restool module names: library1,entry,feature,library');
        expect(result.stdout).toContain('BUILD SUCCESSFUL');

        const executeAgainResult = runHvigor(projectRoot, commands);
        expect(executeAgainResult.stdout).toContain('UP-TO-DATE :entry:default@LegacyProcessResource...');
        expect(executeAgainResult.stdout).toContain('BUILD SUCCESSFUL');
        const resConfigFilePath = path.resolve(projectRoot,
          'entry/build/default/intermediates/res/default/resConfig.json');
        fse.removeSync(resConfigFilePath);

        const executeAgainResultAfterDelete = runHvigor(projectRoot, commands);
        expect(executeAgainResultAfterDelete.stdout).toContain('Finished :entry:default@LegacyProcessResource...');
        expect(result.stdout).toContain('restool module names: entry,feature,library,library1');
        expect(result.stdout).toContain('restool module names: feature,entry,library,library1');
        expect(result.stdout).toContain('restool module names: library,entry,feature,library1');
        expect(result.stdout).toContain('restool module names: library1,entry,feature,library');
        expect(executeAgainResultAfterDelete.stdout).toContain('BUILD SUCCESSFUL');
      });
    });

    test.concurrent('test ProcessLibs- Validate Incremental', async () => {
      await initByFAEmptyApp({
        cb: async (dir) => {

          const commands = initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry',
            tasks: 'default@ProcessLibs',
            log: 'i'
          });

          const resultFirst = runHvigor(dir, commands);
          expect(resultFirst.stdout).toContain('Finished :entry:default@ProcessLibs...');
          expect(resultFirst.stdout).toContain('BUILD SUCCESSFUL');

          const resultIncremental = runHvigor(dir, commands);
          expect(resultIncremental.stdout).toContain('UP-TO-DATE :entry:default@ProcessLibs...');
          expect(resultIncremental.stdout).toContain('BUILD SUCCESSFUL');

          await copy(resolve(appRoot(), 'test/resources/extra/har'), dir);
          const entryPackageJson = new PackageJsonModifier(dir, 'entry');
          entryPackageJson.updateDependency('library', 'file:../library');
          entryPackageJson.outputSync();
          const resultWithAddSo = runHvigor(dir, commands);
          expect(resultWithAddSo.stdout).toContain('Finished :entry:default@ProcessLibs...');
          expect(resultWithAddSo.stdout).toContain('BUILD SUCCESSFUL');

        }
      });
    });
  });

  describe('sync.test', () => {
    test.concurrent('fa app sync', async () => {
      await initByFAEmptyApp(testSync);
    });

    test.concurrent('fa service sync', async () => {
      await initByFAEmptyService(testSync);
    });

    test.concurrent('test fa ets/js super visual path', async () => {
      await initByFAEmptyApp(async (projectDir: string) => {
        const commands = initExecuteCommand({
          sync: true,
          tasks: 'sync'
        });

        // 执行sync
        const result = runHvigor(projectDir, commands);
        expect(result.exitCode).toBe(0);
        const output: string = readSyncOutput();
        expect(output).toContain(
          'entry\\\\build\\\\default\\\\cache\\\\default\\\\default@LegacyCompileArkTS\\\\debug\\\\jsbundle');
        expect(output).toContain(
          'entry\\\\build\\\\default\\\\cache\\\\default\\\\default@LegacyCompileJS\\\\jsbundle');
      });
    });
  });

  describe('trace.test', () => {
    test.concurrent('fa app entry trace', async () => {
      await initByFAEmptyApp(async (projectDir) => {
        await traceTest(projectDir, 'entry');
      });
    });
  });

  describe('build-mode.test', function () {
    describe('project level', function () {
      const testPattern = (dir: string, product: string, command: string[], testBuildOpt: BuildOpt) => {
        const result = runHvigor(dir, command);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

        // 用正则匹配日志中打出的buildOption
        const regResult = RegExp(`Product '${product}' using build option:(.*?) in this build`, 's')
          .exec(result.stdout);
        expect(regResult).not.toBeNull();
        const logBuildOption = JSON.parse(regResult![1]);
        expect(logBuildOption).toEqual(testBuildOpt);
      };

      test.concurrent('should pick right product & buildMode build option', async () => {
        await initByFAEmptyApp({
          prepare: buildModeInitial,
          cb: async (dir) => {
            // buildMode预设值符合预期
            testPattern(dir, 'default', initExecuteCommand({
              mode: 'project',
              product: 'default',
              tasks: 'PreBuildApp',
            }), {
              debuggable: true,
              resOptions: {
                fromProduct: 'default'
              }
            });

            testPattern(dir, 'default', initExecuteCommand({
              mode: 'project',
              product: 'default',
              tasks: 'PreBuildApp',
              buildMode: 'test'
            }), {
              debuggable: true,
              resOptions: {
                fromProduct: 'default'
              }
            });

            // 设置product和build mode，合并符合预期
            testPattern(dir, 'custom', initExecuteCommand({
              mode: 'project',
              product: 'custom',
              tasks: 'PreBuildApp',
              buildMode: 'custom'
            }), {
              resOptions: {
                fromBuildMode: 'custom',
                fromProduct: 'custom'
              }
            });

            // assembleApp会使用release配置
            testPattern(dir, 'custom', initExecuteCommand({
              mode: 'project',
              product: 'custom',
              tasks: 'assembleApp',
            }), {
              debuggable: false,
              resOptions: {
                fromProduct: 'custom'
              }
            });

            // 命令行参数覆盖
            testPattern(dir, 'custom', initExecuteCommand({
              mode: 'project',
              product: 'custom',
              tasks: 'assembleApp',
              debuggable: 'true'
            }), {
              debuggable: true,
              resOptions: {
                fromProduct: 'custom'
              }
            });
          }
        });
      });
    });

    describe('module level', function () {
      const testPattern = (dir: string, module: string, target: string, command: string[], testBuildOpt: BuildOpt) => {
        const result = runHvigor(dir, command);
        expect(result.stdout.includes('BUILD SUCCESSFUL')).toBeTruthy();

        // 用正则匹配日志中打出的buildOption
        const regResult = RegExp(`Module '${module}' target '${target}' using build option:(.*?) in this build`, 's')
          .exec(result.stdout);
        expect(regResult).not.toBeNull();
        const logBuildOption = JSON.parse(regResult![1]);
        expect(logBuildOption).toEqual(testBuildOpt);
      };

      test.concurrent('should pick right target & buildMode build option', async () => {
        await initByFAEmptyApp({
          prepare: buildModeInitial,
          cb: async (dir) => {
            const probes = new Probes(dir, path.resolve(dir, 'entry'));
            new ModuleBuildProfileModifier(dir, 'entry')
              .addBuildModeBinder('custom', 'default', 'custom').outputSync();

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
              buildMode: 'custom'
            }), {
              name: 'custom',
              resOptions: {
                fromProduct: 'default',
                fromBuildOption: 'custom',
                fromBuildMode: 'custom',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });
          }
        });
      });

      test.concurrent('should has right default build option set', async () => {
        await initByFAEmptyApp({
          prepare: buildModeInitial,
          cb: async (dir) => {
            const probes = new Probes(dir, path.resolve(dir, 'entry'));

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
            }), {
              name: 'debug',
              copyFrom: 'default',
              debuggable: true,
              resOptions: {
                fromProduct: 'default',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
              buildMode: 'debug'
            }), {
              name: 'debug',
              copyFrom: 'default',
              debuggable: true,
              resOptions: {
                fromProduct: 'default',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
              buildMode: 'release'
            }), {
              name: 'release',
              copyFrom: 'default',
              debuggable: false,
              resOptions: {
                fromProduct: 'default',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
              buildMode: 'custom'
            }), {
              name: 'default',
              debuggable: true,
              resOptions: {
                fromProduct: 'default',
                'fromBuildMode': 'custom',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });
          }
        });
      });

      test.concurrent('should enable command line input', async () => {
        await initByFAEmptyApp({
          prepare: buildModeInitial,
          cb: async (dir) => {
            new ModuleBuildProfileModifier(dir, 'entry')
              .addBuildModeBinder('custom', 'default', 'custom').outputSync();

            testPattern(dir, 'entry', 'default', initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'assembleHap',
              buildMode: 'custom',
              debuggable: 'false'
            }), {
              name: 'custom',
              debuggable: false,
              resOptions: {
                fromProduct: 'default',
                fromBuildOption: 'custom',
                fromBuildMode: 'custom',
                fromTargetConfig: 'default',
                fromBaseBuildOption: 'base'
              }
            });

            const probes = new Probes(dir, path.resolve(dir, 'entry'));
            const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          }
        });
      });

      test.concurrent('should not have invalid bind', async () => {
        await initByFAEmptyApp({
          prepare: buildModeInitial,
          cb: async (dir) => {
            // test invalid buildOptionName
            const moduleBuildProfileModifier = new ModuleBuildProfileModifier(dir, 'entry');
            moduleBuildProfileModifier.addBuildModeBinder('custom', 'default', 'empty').outputSync();

            const command = initExecuteCommand({
              mode: 'module',
              product: 'default',
              module: 'entry@default',
              tasks: 'default@PreBuild',
            });
            runShouldFail(dir, command, 'Build option \'empty\' used in buildModeBinder is not declared');

            // test invalid targetName
            moduleBuildProfileModifier.addBuildModeBinder('custom', 'empty', 'empty');
            runShouldFail(dir, command, 'Target \'empty\' used in buildModeBinder is not declared');

            // test invalid buildMode
            moduleBuildProfileModifier.addBuildModeBinder('empty', 'empty', 'empty');
            runShouldFail(dir, command, 'Build mode \'empty\' used in buildModeBinder is not declared');
          }
        });
      });
    });
  });
